package kms

import (
	"context"
	"fmt"
	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	kmssdk "github.com/alibabacloud-go/kms-20160120/v3/client"
	teautil "github.com/alibabacloud-go/tea-utils/v2/service"
	"github.com/alibabacloud-go/tea/tea"
	"github.com/aliyun/alibabacloud-kms-agent/internal/model"
	"math"
	"time"
)

const (
	StartUpCheckSecretName = "kms-agent-startup-check"
)

type SMInterface interface {
	GetSecretValue(ctx context.Context, request *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponseBody, error)
	SelfCheck() error
}

type secretManagerClient struct {
	client *kmssdk.Client
}

func newSecretManagerClient(config *openapi.Config) (*secretManagerClient, error) {
	kmsClient, err := kmssdk.NewClient(config)
	if err != nil {
		return nil, fmt.Errorf("failed to create sm client: %v", err)
	}
	return &secretManagerClient{client: kmsClient}, nil
}

func (smClient *secretManagerClient) GetSecretValue(ctx context.Context, request *kmssdk.GetSecretValueRequest) (*kmssdk.GetSecretValueResponseBody, error) {

	runtime := &teautil.RuntimeOptions{}
	runtime.Autoretry = tea.Bool(true)
	runtime.MaxAttempts = tea.Int(3)

	var ret = new(kmssdk.GetSecretValueResponse)
	var err error
	for i := 0; i < 3; i++ {
		ret, err = smClient.client.GetSecretValueWithOptions(request, runtime)
		if err != nil {
			if sdkErr, ok := err.(*tea.SDKError); ok {
				// throttling or unexpected errors
				if *sdkErr.StatusCode == 429 || *sdkErr.StatusCode >= 500 {
					time.Sleep(time.Duration(getWaitTimeExponential(i+1) * int64(time.Millisecond)))
					continue
				}
			}
		}
		break
	}
	if err != nil {
		if sdkErr, ok := err.(*tea.SDKError); ok {
			return nil, &model.ErrorResponse{StatusCode: *sdkErr.StatusCode, ErrorMsg: fmt.Sprintf("failed to get secret [%s]: %v", *request.SecretName, sdkErr.Error())}
		}
		return nil, err
	}
	return ret.Body, nil
}

func (smClient *secretManagerClient) SelfCheck() error {
	ctx := context.Background()
	_, err := smClient.GetSecretValue(ctx, &kmssdk.GetSecretValueRequest{
		SecretName: tea.String(StartUpCheckSecretName),
	})
	if err == nil {
		return nil
	}
	if _, ok := err.(*model.ErrorResponse); ok {
		return nil
	}
	return fmt.Errorf("secret manager client self check falied: %w", err)
}

func getWaitTimeExponential(retryCount int) int64 {
	var initialDelay float64 = 200 //ms
	return int64(math.Pow(2, float64(retryCount)) * initialDelay)
}
